#Region "Diclaimer"
'Copyright (c) 2006, Mark Bracewell (bracewell at wgg dot com)
'All rights reserved.
'Redistribution and use in source and binary forms, with or without
'modification, are permitted provided that the following conditions are met: 
'*

'* Redistributions of source code must retain the above copyright
'notice, this list of conditions and the following disclaimer.
'* Redistributions in binary form must reproduce the above copyright
'notice, this list of conditions and the following disclaimer in the
'documentation and/or other materials provided with the distribution.
'* The name Mark Bracewell may not be used to endorse or promote products
'derived from this software without specific prior written permission. 
'* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY
'EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
'WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
'DISCLAIMED. IN NO EVENT SHALL MARK BRACEWELL BE LIABLE FOR ANY
'DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
'(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
'LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
'ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
'(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
'SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
#End Region

Imports Microsoft.VisualBasic
Imports System.Data.Sql
Imports System.Data.SqlClient
Imports SubSonic
Imports ClubStarterKit.Data
Imports System.Data
Imports System.Configuration
Imports System.Collections.Generic
Imports System.Collections.Specialized

Namespace ClubStarterKit.Web
    Public Class Forum
        Public Const MAXNEWMESSAGEAGEDAYS As Integer = -90
        Public Const MAXRECENTMESSAGEAGEHOURS As Integer = 24




#Region "Threads"
        Public Shared Function GetThreadLock(ByVal threadid As Integer) As Boolean
            Dim thread As New Threads(Threads.Columns.Threadid, threadid)
            If thread.IsLoaded = True Then
                Return thread.Locked
            Else
                Return False
            End If
        End Function

        <System.ComponentModel.DataObjectMethodAttribute _
                (System.ComponentModel.DataObjectMethodType.Select, False)> _
                Public Shared Function Load(ByVal msgid As Integer) As ForumMessage
            If msgid < 1 Then Return Nothing

            Dim rdr As IDataReader = SPs.GetMessage(msgid).GetReader()
            rdr.Read()
            Dim msg As ForumMessage = GetMessage(rdr)
            rdr.Close()

            Return msg
        End Function

        Public Shared Function GetMessage(ByVal reader As SqlDataReader) As ForumMessage
            Dim msg As New ForumMessage
            If reader.IsClosed Then
                reader.Read()
            End If

            msg.body = reader("body").ToString
            msg.subject = reader("subject").ToString
            msg.threadid = CInt(reader("threadid"))
            msg.parentmsgid = CInt(reader("parentmsgid"))
            msg.memberid = DirectCast(reader("memberid"), Guid)
            msg.msgid = CInt(reader("id"))
            msg.topicid = CInt(reader("topicid"))
            msg.displayname = reader("displayname").ToString
            msg.postdate = DirectCast(reader("postdate"), Date)
            msg.answer = CBool(reader("answer"))
            Return msg
        End Function

        Public Shared Function GetThreadWatch(ByVal threadid As Integer, ByVal memberid As Guid) As Boolean
            Dim qry As New Query(Tables.MembersThreads)
            qry.AddWhere(MembersThreads.Columns.Threadid, threadid)
            qry.AddWhere(MembersThreads.Columns.Memberid, memberid)
            qry.SelectList = MembersThreads.Columns.Watch
            qry.QueryType = QueryType.Select

            Dim retval As Boolean = CType(qry.ExecuteScalar(), Boolean)
            Return retval
        End Function

        Public Shared Function Add(ByVal msg As ForumMessage, ByVal AdminOnly As Boolean) As Integer
            msg.displayname = GetCachedForumUser(msg.memberid).DisplayName()
            ClearCache(msg.memberid)
            Dim threadid As Integer = SPs.AddMessage(msg.parentmsgid, msg.memberid, msg.body, msg.subject, AdminOnly, msg.topicid).Execute()
            msg.threadid = threadid
            msg.postdate = Now
            ForumNotification.Notify(msg)
            StartModeration(msg)
            Return threadid
        End Function

        Public Shared Sub MarkThreadRead(ByVal threadid As Integer, ByVal memberid As Guid)
            SPs.MarkThreadRead(threadid, memberid).Execute()
        End Sub

        Public Shared Sub SetThreadWatch(ByVal threadid As Integer, ByVal memberid As Guid, ByVal watch As Boolean)
            Dim qry As New Query(Tables.MembersThreads)
            qry.QueryType = QueryType.Update
            qry.AddUpdateSetting(MembersThreads.Columns.Watch, watch)
            qry.AddWhere(MembersThreads.Columns.Threadid, threadid)
            qry.AddWhere(MembersThreads.Columns.Memberid, memberid)
            qry.Execute()
        End Sub

        Public Shared Sub Delete(ByVal msgid As Integer)
            SPs.DeleteMessage(msgid).Execute()
        End Sub

        <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Delete, False)> _
        Public Shared Sub Delete(ByVal msg As ForumMessage)
            Delete(msg.msgid)
        End Sub

        <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Update, False)> _
        Public Shared Sub SaveEdit(ByVal msg As ForumMessage)
            SPs.SaveEditMessage(msg.msgid, msg.body, msg.subject).Execute()
        End Sub

        Public Shared Function GetThreadSticky(ByVal threadid As Integer) As Boolean
            Dim Thread As New Threads(Threads.Columns.Threadid, threadid)
            Return Thread.Sticky
        End Function

        Public Shared Sub DeleteThread(ByVal threadid As Integer)
            Dim qry As New Query(Tables.Threads)
            qry.QueryType = QueryType.Delete
            qry.AddWhere(Threads.Columns.Threadid, threadid)
            qry.Execute()
        End Sub

        Public Shared Sub SetThreadLock(ByVal threadid As Integer, ByVal locked As Boolean)
            Dim qry As New Query(Tables.Threads)
            qry.AddWhere(Threads.Columns.Threadid, threadid)
            qry.AddUpdateSetting(Threads.Columns.Locked, locked)
            qry.QueryType = QueryType.Update
            qry.Execute()
        End Sub

        Public Shared Sub SetThreadSticky(ByVal threadid As Integer, ByVal sticky As Boolean)
            Dim qry As New Query(Tables.Threads)
            qry.AddWhere(Threads.Columns.Threadid, threadid)
            qry.AddUpdateSetting(Threads.Columns.Sticky, sticky)
            qry.QueryType = QueryType.Update
            qry.Execute()
        End Sub

        Public Shared Function IsRecentPost(ByVal lastpostdate As Object) As Boolean
            If lastpostdate Is Nothing Or IsDBNull(lastpostdate) Then Return False
            Return DateAdd(DateInterval.Hour, MAXRECENTMESSAGEAGEHOURS, CDate(lastpostdate)) > Now
        End Function

        Public Shared Function RecentPostAge(ByVal lastpostdate As Object) As String
            If Not IsRecentPost(lastpostdate) Then Return ""
            Dim age As String
            Dim secs As Long = DateDiff(DateInterval.Second, CDate(lastpostdate), Now)
            Select Case secs
                Case Is < 60
                    age = "Less than 1 Minute ago"
                Case Is < 3600
                    Dim nn As Double = Math.Floor(secs / 60)
                    age = String.Format("{0} Minute{1} ago", nn.ToString(), IIf(nn = 1, "", "s"))
                Case Else
                    Dim hh As Double = Math.Floor(secs / 3600)
                    Dim hoursage As String = String.Format("{0} Hour{1}", hh.ToString(), IIf(hh = 1, "", "s"))
                    Dim mm As Double = Math.Floor((secs Mod 3600) / 60)
                    Dim minsage As String = String.Format(", {0} Minute{1}", mm.ToString(), IIf(mm = 1, "", "s"))
                    age = String.Format("{0}{1} ago", hoursage, IIf(mm = 0, "", minsage))
            End Select
            Return age
        End Function

        Public Shared Function IsThreadUnread(ByVal threadid As Integer, ByVal memberid As Guid, ByVal lastpostdate As Date) As Boolean
            Return GetLastTimeReadThread(threadid, memberid) < lastpostdate
        End Function

        Public Shared Function GetLastTimeReadThread(ByVal threadid As Integer, ByVal memberid As Guid) As Date
            Dim newmsgthreshold As Object = DateAdd(DateInterval.Day, MAXNEWMESSAGEAGEDAYS, Now)
            Dim MemProf As New MemberProfile(MemberProfile.Columns.Memberid, memberid)
            If HttpContext.Current.Items("newmsgthreshold") Is Nothing Then

                Dim qry As New Query(Tables.MembersThreads)
                qry.QueryType = QueryType.Select
                qry.AddWhere(MembersThreads.Columns.Memberid, memberid)
                qry.SelectList = MembersThreads.Columns.Threadid & "," & MembersThreads.Columns.Lastread

                Dim reader As SqlDataReader = qry.ExecuteReader()
                While reader.Read()
                    HttpContext.Current.Items.Add("threadlastread" & reader.GetInt32(0).ToString(), MemProf.Newmsgthreshold)
                End While
                If reader.NextResult() Then
                    If reader.HasRows() Then
                        reader.Read()
                        newmsgthreshold = MemProf.Newmsgthreshold
                        If newmsgthreshold Is Nothing Or IsDBNull(newmsgthreshold) Then
                            newmsgthreshold = DateAdd(DateInterval.Day, MAXNEWMESSAGEAGEDAYS, Now)
                        End If
                    End If
                End If

                HttpContext.Current.Items.Add("newmsgthreshold", newmsgthreshold)
            End If
            Dim last As Object = HttpContext.Current.Items("threadlastread" & threadid.ToString())
            If Not last Is Nothing Then
                Return CType(last, Date)
            End If
            Return CType(HttpContext.Current.Items("newmsgthreshold"), Date)
        End Function

        Public Shared Function IsAnswered(ByVal threadid As Integer) As Boolean
            Dim qry As New Query(Tables.Messages)
            qry.AddWhere(Messages.Columns.Threadid, threadid)
            qry.AddWhere(Messages.Columns.Answer, True)
            Dim Count As Integer = qry.GetRecordCount()
            If Count > 0 Then
                Return True
            Else
                Return False
            End If
        End Function

        Public Shared Sub MarkAnswer(ByVal messageid As Integer)
            Dim qry As New Query(Tables.Messages)
            qry.QueryType = QueryType.Update
            qry.AddWhere(Messages.Columns.Id, messageid)
            qry.AddUpdateSetting(Messages.Columns.Answer, True)
            qry.Execute()
        End Sub

        Public Shared Sub UnmarkAnswer(ByVal messageid As Integer)
            Dim qry As New Query(Tables.Messages)
            qry.QueryType = QueryType.Update
            qry.AddWhere(Messages.Columns.Id, messageid)
            qry.AddUpdateSetting(Messages.Columns.Answer, False)
            qry.Execute()
        End Sub

        Public Shared Function IsAdminOnlyThread(ByVal threadid As Integer) As Boolean
            Dim Thread As New Threads(Threads.Columns.Threadid, threadid)
            Return Thread.Admin
        End Function

        Public Shared Function NumAnswers(ByVal memid As Guid) As Integer
            Dim qry As New Query(Tables.Messages)
            qry.AddWhere(Messages.Columns.Answer, True)
            qry.AddWhere(Messages.Columns.Memberid, memid)
            qry.QueryType = QueryType.Select
            Return qry.GetRecordCount()
        End Function

        Public Shared Function GetMemGuidByMsg(ByVal messageid As Integer) As Guid
            Dim Message As New Messages(Messages.Columns.Id, messageid)
            If Message.IsLoaded Then
                Return Message.Memberid
            Else
                Return Nothing
            End If
        End Function

        Public Shared Function IsFirstMessage(ByVal messageid As Integer) As Boolean
            Dim Message As New Messages(Messages.Columns.Id, messageid)
            If Message.IsLoaded Then
                If Message.Parentmsgid = 0 Then
                    Return False
                Else
                    Return True
                End If
            Else
                Return False
            End If
        End Function

        Public Shared Sub Report(ByVal messageid As Integer, ByVal reason As String, ByVal CurrentMem As MembershipUser, ByVal URL As String)
            Dim msg As ForumMessage = Load(messageid)
            Dim Topic As New Topics(Topics.Columns.Topicid, msg.topicid)
            Dim ModeratorGUID As Guid = Topic.Moderatorkey
            Dim Mem As MembershipUser = Membership.GetUser(ModeratorGUID)
            SpamFlag.Insert(messageid, CurrentMem.ProviderUserKey, Mem.ProviderUserKey, reason, False)

            Dim EmailBody As String = "<h2>Forum Moderation</h2><br />"
            EmailBody += "A user has requested that you review this message that was posted in the "
            EmailBody += Topic.Topic
            EmailBody += " topic. Please review the message and moderate accordingly.<br /><br /><strong>Message Text</strong><br />"
            EmailBody += msg.body
            EmailBody += "<br /><br />The user who submited this request has given the following reason for their report of this message:<br />"
            EmailBody += reason
            EmailBody += "You can view the message in question on <a href='" & URL & "'>this page</a>.<br />"
            EmailBody += "<a href='" & ClubStarterKit.Web.Settings.SiteURL() & "forum/editpost.aspx?ms=" & messageid.ToString() & "'>Moderate Message</a><br /><br />"
            EmailBody += "<a href='" & ClubStarterKit.Web.Settings.SiteURL() & "forum/report.aspx?reportid=" & messageid.ToString() & "'>Mark As Resolved</a> "
            ClubStarterKit.Web.ForumNotification.ModerateEmail(Mem.Email, CurrentMem.Email, EmailBody)
        End Sub

        Public Shared Sub MarkReportAsResolved(ByVal reportid As String)
            Dim qry As New Query(Tables.SpamFlag)
            qry.AddWhere(SpamFlag.Columns.Id, reportid)
            qry.AddUpdateSetting(SpamFlag.Columns.IsResolved, True)
            Dim rpt As New SpamFlag(SpamFlag.Columns.Id, reportid)
            ClubStarterKit.Web.ForumNotification.ReportResolve(rpt)
        End Sub

        Public Shared Sub StartModeration(ByVal msg As ForumMessage)
            Dim shouldmod As Boolean = False
            Select Case ClubStarterKit.Web.Settings.ModerationLevel()
                Case 1
                    If msg.parentmsgid = 0 Then
                        shouldmod = True
                    Else
                        shouldmod = False
                    End If
                Case 2
                    shouldmod = True
                Case Else
            End Select

            If shouldmod = True Then
                Dim thread As New Threads(Threads.Columns.Threadid, msg.threadid)
                Dim topid As Integer = thread.Topicid
                Dim topic As New Topics(Topics.Columns.Topicid, topid)
                Dim ModMem As MembershipUser = Membership.GetUser(topic.Moderatorkey)
                ClubStarterKit.Web.ForumNotification.ModeratePost(ModMem.Email, msg)
            End If
        End Sub

        Public Shared Sub MarkAllRead(ByVal memberid As Guid)
            SPs.MarkAllRead(memberid).Execute()
        End Sub

        Public Shared Function WatcherList(ByVal threadid As Integer) As List(Of Member)
            Dim userList As New List(Of Member)

            Dim qry As New Query(Tables.MembersThreads)
            qry.QueryType = QueryType.Select
            qry.SelectList = MembersThreads.Columns.Memberid
            qry.AddWhere(MembersThreads.Columns.Threadid, threadid)
            qry.AddWhere(MembersThreads.Columns.Watch, True)
            Dim reader As IDataReader = qry.ExecuteReader()
            While reader.Read()
                userList.Add(GetCachedForumUser(reader.GetGuid(0)))
            End While

            Return userList
        End Function
#End Region

#Region "Topics"
        <System.ComponentModel.DataObjectMethodAttribute _
                        (System.ComponentModel.DataObjectMethodType.Select, False)> _
                        Public Shared Function GetTopics() As IDataReader
            Dim qry As New Query(Tables.Topics)
            qry.QueryType = QueryType.Select
            qry.OrderBy = OrderBy.Asc(Topics.Columns.Weight)
            qry.SelectList = Topics.Columns.Topicid & "," & Topics.Columns.Topic & "," & Topics.Columns.Description & "," & Topics.Columns.Section & "," & Topics.Columns.Weight & "," & Topics.Columns.Locked & "," & Topics.Columns.Visible & "," & Topics.Columns.Moderatorkey
            Dim rdr As IDataReader = qry.ExecuteReader()
            Return rdr
            rdr.Close()
        End Function

        Public Shared Function GetTopicInfo(ByVal topicid As Integer) As ForumTopicInfo
            Dim ti As New ForumTopicInfo
            Dim qry As New Query(Tables.Topics)
            qry.AddWhere(Topics.Columns.Topicid, topicid)
            qry.SelectList = Topics.Columns.Topic & "," & Topics.Columns.Description & "," & Topics.Columns.Visible & "," & Topics.Columns.Locked & "," & Topics.Columns.Section & "," & Topics.Columns.Moderatorkey
            Dim rdr As IDataReader = qry.ExecuteReader()
            If rdr.Read() Then
                ti.topic = rdr.GetString(0)
                ti.description = rdr.GetString(1)
                ti.visible = rdr.GetBoolean(2)
                ti.locked = rdr.GetBoolean(3)
                ti.section = rdr.GetBoolean(4)
                ti.moderatorkey = rdr.GetGuid(5)
                ti.topicid = topicid
            End If
            rdr.Close()
            Return ti
        End Function

        <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Delete, False)> _
        Public Shared Sub DeleteTopic(ByVal TopicID As Integer)
            Topics.Delete(Topics.Columns.Topicid, TopicID)
        End Sub

        <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Update, False)> _
        Public Shared Sub UpdateTopic(ByVal TopicID As String, ByVal Topic As String, ByVal Description As String, ByVal Section As Boolean, ByVal Weight As Integer, ByVal Locked As Boolean, ByVal Visible As Boolean, ByVal moderatorkey As String)
            Dim qry As New Query(Tables.Topics)
            qry.QueryType = QueryType.Update
            qry.AddWhere(Topics.Columns.Topicid, TopicID)
            qry.AddUpdateSetting(Topics.Columns.Topic, Topic)
            qry.AddUpdateSetting(Topics.Columns.Description, Description)
            qry.AddUpdateSetting(Topics.Columns.Section, Section)
            qry.AddUpdateSetting(Topics.Columns.Weight, Weight)
            qry.AddUpdateSetting(Topics.Columns.Locked, Locked)
            qry.AddUpdateSetting(Topics.Columns.Visible, Visible)
            qry.AddUpdateSetting(Topics.Columns.Moderatorkey, moderatorkey)
            qry.Execute()
        End Sub

        <System.ComponentModel.DataObjectMethodAttribute _
        (System.ComponentModel.DataObjectMethodType.Insert, False)> _
        Public Shared Sub AddTopic(ByVal Topic As String, ByVal Description As String, ByVal Section As Boolean, ByVal Weight As Integer, ByVal Locked As Boolean, ByVal Visible As Boolean, ByVal memkey As String)
            Dim mem1 As New AspnetUsers(AspnetUsers.Columns.UserId, memkey)
            Dim mem As MembershipUser = Membership.GetUser(mem1.UserName)
            Topics.Insert(Topic, Description, Section, Weight, Locked, Visible, mem.ProviderUserKey())
        End Sub

        Public Shared Function GetTopicInfoByThread(ByVal threadid As Integer) As ForumTopicInfo
            Dim Thread As New Threads(Threads.Columns.Threadid, threadid)
            Dim TopicID As Integer = Thread.Topicid
            Return GetTopicInfo(TopicID)
        End Function

        Public Shared Function TopicHasUnreadMessages(ByVal topicid As Integer, ByVal memberid As Guid) As Boolean
            If HttpContext.Current.Items("topicsunread") Is Nothing Then
                Dim qry As New Query(Tables.Threads)
                qry.QueryType = QueryType.Select
                qry.SelectList = Threads.Columns.Threadid & "," & Threads.Columns.Topicid & "," & Threads.Columns.Lastpostdate
                Dim reader As IDataReader = qry.ExecuteReader()
                While reader.Read()
                    Dim thrid As Integer = reader.GetInt32(0)
                    Dim topid As String = reader.GetInt32(1).ToString()
                    Dim lpd As Date = reader.GetDateTime(2)
                    If HttpContext.Current.Items("unreadtopic" & topid) Is Nothing Then
                        If IsThreadUnread(thrid, memberid, lpd) Then
                            HttpContext.Current.Items.Add("unreadtopic" & topid, lpd)
                        End If
                    End If
                End While
                HttpContext.Current.Items.Add("topicsunread", "okie dokie")
                reader.Close()
            End If
            Dim last As Object = HttpContext.Current.Items("unreadtopic" & topicid.ToString())
            If Not last Is Nothing Then
                Return True
            End If
            Return False
        End Function
#End Region

#Region "Users"
        Public Shared Function GetForumUser(ByVal memberid As Object) As Member
            Return New Member(memberid)
        End Function

        Public Shared Function GetCachedForumUser(ByVal memberid As Object) As Member
            Dim fUser As Member
            If HttpContext.Current.Items("fu") Is Nothing Then
                fUser = Nothing
            Else
                fUser = CType(HttpContext.Current.Cache.Get("fu" & memberid.ToString()), Member)
            End If
            If Not fUser Is Nothing Then Return fUser
            fUser = New Member(memberid)
            HttpContext.Current.Cache.Add("fu" & memberid.ToString(), fUser, Nothing, DateAdd(DateInterval.Second, 90, Now), System.Web.Caching.Cache.NoSlidingExpiration, Caching.CacheItemPriority.Normal, Nothing)
            Return fUser
        End Function

        Public Shared Function GetCurrentForumUser() As Member
            Return GetCachedForumUser(Membership.GetUser.ProviderUserKey)
        End Function

        Public Shared Sub ClearCache(ByVal memberid As Object)
            HttpContext.Current.Cache.Remove("fu" & memberid.ToString())
        End Sub
#End Region

    End Class
End Namespace